package com.common.mybatis.plugins;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.common.mybatis.plugins.pagination.Pagination;
import com.common.mybatis.plugins.pagination.dialect.DialectFactory;
import com.common.mybatis.plugins.pagination.dialect.PageDialect;

/**
 * 分页插件 
 * 使用方法示例： List<?> queryByPage(RowBounds rowBounds,@Param("id")String id)
 * 即方法的参数里加上 RowBounds rowBounds 即可
 * 
 * @author songchangyou
 * @Date 2016年9月16日
 *
 */
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = {
    Connection.class}) })
public class PaginationInterceptor implements Interceptor {
  private static final Logger LOG = LoggerFactory.getLogger(PaginationInterceptor.class);

  /**
   * 方言类型
   */
  private String dialectType;

  /**
   * 方言实现类
   */
  private String dialectClazz;

  public Object intercept(Invocation invocation) throws Throwable {
    Object target = invocation.getTarget();
    if (target instanceof StatementHandler) {
      StatementHandler statementHandler = (StatementHandler) target;
      MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
      Object obj = metaStatementHandler.getValue("delegate.rowBounds");
      if (obj != null && obj instanceof RowBounds) {
        RowBounds rowBounds = (RowBounds) obj;
        // 不是空或者默认值，分页
        if (rowBounds != null && rowBounds != RowBounds.DEFAULT) {
          /* 定义数据库方言 */
          PageDialect dialect = null;
          if (StringUtils.isNotBlank(dialectClazz)) {
            try {
              Class<?> clazz = Class.forName(dialectClazz);
              if (PageDialect.class.isAssignableFrom(clazz)) {
                dialect = (PageDialect) clazz.newInstance();
              }
            } catch (ClassNotFoundException e) {
              throw new RuntimeException("类 :" + dialectClazz + " 未找到");
            }
          } else if (StringUtils.isNotBlank(dialectType)) {
            dialect = DialectFactory.getDialectByDbtype(dialectType);
          }

          /* 未配置方言则抛出异常 */
          if (dialect == null) {
            throw new RuntimeException("请在spring-mybatis.xml中配置分页插件中配置dialect属性");
          }

          /*
           * 禁用内存分页 
           */
          BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
          String originalSql = (String) boundSql.getSql();
          metaStatementHandler.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET);
          metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);

          /*
           * 分页逻辑 查询总记录数 count
           */
          if (rowBounds instanceof Pagination) {
            MappedStatement mappedStatement = (MappedStatement) metaStatementHandler
                .getValue("delegate.mappedStatement");
            Connection connection = (Connection) invocation.getArgs()[0];
            Pagination page = (Pagination) rowBounds;
            if(page.isQueryCount()){
            	this.count(originalSql, connection, mappedStatement, boundSql,page);
            }
            originalSql = dialect.buildPaginationSql(originalSql, page.getOffsetCurrent(),
                page.getPageSize());
          }
          /*
           * 查询 SQL 设置
           */
          metaStatementHandler.setValue("delegate.boundSql.sql", originalSql);
        }
      }
    }
    return invocation.proceed();
  }

  /**
   * 查询总记录条数
   * 
   * @param sql
   * @param connection
   * @param mappedStatement
   * @param boundSql
   * @param page
   */
  public void count(String sql, Connection connection, MappedStatement mappedStatement,
      BoundSql boundSql, Pagination page) {
    String countSqlStr = sql;
    int orderByIndex = sql.toUpperCase().lastIndexOf("ORDER BY");
    if (orderByIndex > -1) {
      countSqlStr = sql.substring(0, orderByIndex);
    }
    StringBuffer countSql = new StringBuffer("SELECT COUNT(1) FROM (");
    countSql.append(countSqlStr).append(") AS TOTAL");
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try {
      pstmt = connection.prepareStatement(countSql.toString());
      BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql.toString(),
          boundSql.getParameterMappings(), boundSql.getParameterObject());
      ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement,
          boundSql.getParameterObject(), countBS);
      parameterHandler.setParameters(pstmt);
      rs = pstmt.executeQuery();
      int total = 0;
      if (rs.next()) {
        total = rs.getInt(1);
      }
      page.setTotal(total);
      /**
       * 当前页大于总页数，当前页设置为最后一页
       */
      if (page.getPageNumber() > page.getTotalPageCount()) {
        page = new Pagination(page.getTotalPageCount(), page.getPageSize());
        page.setTotal(total);
      }
    } catch (SQLException e) {
      LOG.error("",e);
    } finally {
      try {
        rs.close();
        pstmt.close();
      } catch (SQLException e) {
        LOG.error("",e);
      }
    }
  }

  public Object plugin(Object target) {
    if (target instanceof StatementHandler) {
      return Plugin.wrap(target, this);
    }
    return target;
  }

  public void setProperties(Properties properties) {
    String dialectType = properties.getProperty("dialectType");
    String dialectClazz = properties.getProperty("dialectClazz");
    if (StringUtils.isNotBlank(dialectType)) {
      this.dialectType = dialectType;
    }
    if (StringUtils.isNotBlank(dialectClazz)) {
      this.dialectClazz = dialectClazz;
    }
  }

}
